#include "defaults.h"
#include "nurbs.h"
#include "utils.h"
#include "nurbssurfacemesh.h"
#include "primitivemanager.h"

NurbsSurfaceMesh::NurbsSurfaceMesh(size_t nodes, 
								   const std::vector< std::pair<std::string, Variant> >& vertexdata, 
								   const std::vector< std::pair<std::string, Variant> >& nurbsdata, 
								   const std::vector< boost::multi_array<size_t, 2> >& nurbsIndex,
								   const std::vector< std::pair<Knotvector, Knotvector> >& knotvector,
								   size_t p, size_t q) {
	Init(nodes, vertexdata, nurbsdata, nurbsIndex, knotvector, p, q);
}

Node* NurbsSurfaceMesh::AllocateVertex() {
	Node* v = new Node(nodeAttribute, vertexidcounter++);
	vertex.push_back(v);
	return v;
}

SNurbs* NurbsSurfaceMesh::AllocateNurbs(const boost::multi_array<Node*, 2>& c, const Knotvector& U, const Knotvector& V, size_t p, size_t q) {
	SNurbs* n = new SNurbs(c, U, V, p, q, elementAttribute, nurbsidcounter++);
	nurbs.push_back(n);
	return n;
}

void SetNeighbors(boost::multi_array<Node*, 2>& c) {
	const size_t n = c.shape()[0];
	const size_t m = c.shape()[1];

	for(size_t i = 0; i < n; i++) {
		for(size_t j = 0; j < m; j++) {
			if(i > 0)     c[i][j]->AddNeighbor(c[i - 1][j]);
			if(i < n - 1) c[i][j]->AddNeighbor(c[i + 1][j]);
			
			if(j > 0)     c[i][j]->AddNeighbor(c[i][j - 1]);
			if(j < m - 1) c[i][j]->AddNeighbor(c[i][j + 1]);
		}
	}
}

void NurbsSurfaceMesh::Init(size_t nodes, 
							const std::vector< std::pair<std::string, Variant> >& vertexdata, 
							const std::vector< std::pair<std::string, Variant> >& nurbsdata, 
							const std::vector< boost::multi_array<size_t, 2> >& nurbsIndex,
							const std::vector< std::pair<Knotvector, Knotvector> >& knotvector,
							size_t p, size_t q) {
	vertexidcounter = 0;
	nurbsidcounter = 0;
	
	size_t elements = nurbsIndex.size();
	
	for(size_t i = 0; i < vertexdata.size(); i++) {
		const std::string& attribute = vertexdata[i].first;			
		const Variant& attributeData = vertexdata[i].second;
		nodeAttribute.Add(attribute, attributeData);
	}
	
	for(size_t i = 0; i < nurbsdata.size(); i++) {
		const std::string& attribute = nurbsdata[i].first;
		const Variant& attributeData = nurbsdata[i].second;
		elementAttribute.Add(attribute, attributeData);
	}
	
	for(size_t i = 0; i < nodes; i++) {
		Node* v = AllocateVertex();
	}
	
	bool flag = true;
	for(size_t k = 0; k < elements; k++) {
		const boost::multi_array<size_t, 2>& index = nurbsIndex[k];
		boost::multi_array<Node*, 2> c(boost::extents[p + 1][q + 1]);
		
		for(size_t i = 0; i <= p; i++) {
			for(size_t j = 0; j <= q; j++) {
				c[i][j] = static_cast<Node*>(GetNode(index[i][j]));
			}
		}

		boost::multi_array<Node*, 1> cu1(boost::extents[p + 1]);
		boost::multi_array<Node*, 1> cu2(boost::extents[p + 1]);
		boost::multi_array<Node*, 1> cv1(boost::extents[q + 1]);
		boost::multi_array<Node*, 1> cv2(boost::extents[q + 1]);
		for(size_t i = 0; i <= p; i++) {
			cu1[i] = c[i][0];
			cu2[i] = c[i][q];
		}
		for(size_t i = 0; i <= q; i++) {
			cv1[i] = c[0][i];
			cv2[i] = c[p][i];
		}
		
		const Knotvector& U = knotvector[k].first;
		const Knotvector& V = knotvector[k].second;
	
		SNurbs* n = AllocateNurbs(c, U, V, p, q);		
		SetNeighbors(c);
	}
	
	for(size_t i = 0; i < nurbs.size(); i++)
		nurbs[i]->Init();

	//////////////////////////////////////////////////////////////////////////////////////////////////////


	std::cout << "nurbs patches read." << std::endl;
}

void NurbsSurfaceMesh::Save(const std::string& filename, const std::string& attribute) const {
	std::ofstream file;
	file.open(filename.c_str());
	file << std::setprecision(8);
	file << "nurbssurfacemesh" << std::endl;
	for(size_t i = 0; i < SizeNodes(); i++) {
		const Node* v = GetNode(i);
		const Vec3n& P = v->Get<Vec3n>("position");
		file << "position " << P[0] << " " << P[1] << " " << P[2] << std::endl; 
	}
	for(size_t i = 0; i < SizeElements(); i++) {
		const SNurbs* e = static_cast<const SNurbs*>(GetElement(i));

		file << "element " << e->Id() << std::endl;

		file << e->GetDegreeU() << " " << e->GetDegreeV() << std::endl;

		const Knotvector& U = e->GetKnotvectorU();
		for(size_t j = 0; j < U.size(); j++)
			if(j == U.size() - 1) file << U[j]; else file << U[j] << " ";
		file << std::endl;
		
		const Knotvector& V = e->GetKnotvectorV();
		for(size_t j = 0; j < V.size(); j++)
			if(j == V.size() - 1) file << V[j]; else file << V[j] << " ";
		file << std::endl;

		for(size_t j = 0; j <= e->GetDegreeU(); j++) {
			for(size_t k = 0; k <= e->GetDegreeV(); k++) {
				const Node* v = e->GetNode(j, k);
 				if(k == e->GetDegreeV()) file << v->Id(); else file << v->Id() << " ";
			}
			file << std::endl;
		}
	}

	file.close();
}
